//
//  JWLandscape.m
//  KotH
//
//  Created by James Weatherley on 01/08/2008.
//  Copyright 2008 James Weatherley. All rights reserved.
//

#import "../koth.h"
#import "../wolfiphone.h"
#import "kothConstants.h"
#import <OpenGLES/ES1/gl.h>


#pragma mark JWLandscapeCoords
// Lightweight square for archival.
@interface JWLandscapeCoords : NSObject <NSCoding>
{
	int x;
	int z;
	int count;
	float angle;
} 
@property (readwrite) int x;
@property (readwrite) int z;
@property (readwrite) int count;
@property (readwrite) float angle;

-(id)initWithSquare:(JWSquare*)square;

@end


@implementation JWLandscapeCoords

@synthesize x;
@synthesize z;
@synthesize count;
@synthesize angle;

-(id)initWithSquare:(JWSquare*)square
{
	self = [super init];
	x = square.x;
	z = square.z;
	
	if([square.boxes count]) {
		count = [square.boxes count];
	} else {
		count = 1;
	}
	
	if(square.sentinel) {
		angle = square.sentinel.theta;
	} else {
		angle = -1.0f;
	}
	
	return self;
}

-(id)initWithCoder:(NSCoder*)decoder
{
	self = [super init];
	x = [decoder decodeIntForKey:@"x"];
	z = [decoder decodeIntForKey:@"z"];
	count = [decoder decodeIntForKey:@"count"];
	angle = [decoder decodeFloatForKey:@"angle"];
	
	return self;
}

-(void)encodeWithCoder:(NSCoder*)encoder
{
	[encoder encodeInt:x forKey:@"x"];
	[encoder encodeInt:z forKey:@"z"];
	[encoder encodeInt:count forKey:@"count"];
	[encoder encodeFloat:angle forKey:@"angle"];
}
@end

#pragma mark -
#pragma mark  JWLandscape private methods/
@interface JWLandscape (PrivateMethods)

-(void)analyzeLandscape;
-(BOOL)hasFlatNeighbour:(JWSquare*)square;
-(NSMutableArray*)emptySquares;
-(void)plantTrees;
-(void)calculateSquareVertices;
-(void)populateLandscape:(NSArray*)positions type:(Class)aClass;
-(void)setMaxXandMaxZ;
-(void)setSentinelCount;
-(void)addExtraSentinels;

@end

#pragma mark JWLandscape
@implementation JWLandscape

#pragma mark synthetic
@synthesize maxX;
@synthesize maxZ;
@synthesize level;
@synthesize lowSquares;
@synthesize highSquares;
@synthesize landscapeMode;
@synthesize selectedSquare;
@synthesize highSquare;


#pragma mark Initialisers
//-(id)initWithLength:(NSUInteger)length_ width:(NSUInteger)width_ level:(NSUInteger)level_ populate:(BOOL)populate_
-(id)initWithLevel:(NSUInteger)level_ populate:(BOOL)populate_
{
	self = [super init];
	
	//maxX = length_;
	//maxZ = width_;
	level = level_;
	[self setMaxXandMaxZ];
	[self setSentinelCount];
	
	landscape = [[NSMutableArray alloc] initWithCapacity:maxX * maxZ];
	
	for(int z = 0; z < maxZ; ++z) {
		for(int x = 0; x < maxX; ++x) {
			[landscape addObject:[[[JWSquare alloc] initWithPositionX:x z:z height:0 number:z * maxX+ x] autorelease]];
		}
	}
	
	landscapeMode = JWLandscapeSmooth;
	lowSquares = [[NSMutableArray alloc] init];
	highSquares = [[NSMutableArray alloc] init];
	
	// Add trees, player, and sentinel if required.
	[self generateWithTrees:populate_];
	
	JWTreeManager* treeManager = (JWTreeManager*)[[JWManagers sharedManager] managerOfClass:[JWTreeManager class]];
	
	if(populate_) {
		// Place a sentinel on the highest square.
		NSUInteger randomIndex = randomInt([highSquares count]);
		highSquare = [highSquares objectAtIndex:randomIndex];
		JWSentinel* sentinel = [[[JWSentinel alloc] initWithSquare:highSquare] autorelease];
		[[sentinel manager] addEntity:sentinel];
		sentinel.sentinelId = 0;
		highSquare.tree = nil;
		highSquare.endOfLevel = YES;
		
		// Place player on the lowest square.
		randomIndex = randomInt([lowSquares count]);
		JWSquare* lowSquare = [lowSquares objectAtIndex:randomIndex];
		Player.position.origin[0] = lowSquare.x;
		Player.position.origin[1] = -lowSquare.z;
		Player.position.height = lowSquare.height + 1.0f;
		Player.health = 10;
		
		lowSquare.player = YES;
		lowSquare.tree = nil;
		
		// The player or sentinel may have replaced a tree...
		[treeManager updateTrees];
		
		// Add any extra sentinels.
		[self addExtraSentinels];
		[JWSentinel clearMasks];
	}
	
	[treeManager updateVertexArrays];
	
	// Calculate the landscape's geometry.
	[self calculateSquareVertices];
	setLevelColours(level);
	return self;
}

-(void)dealloc
{
	[super dealloc];
	[landscape release];
	[lowSquares release];
	[highSquares release];
}

#pragma mark -
#pragma mark Overrides
-(NSString*)description
{
	NSString* line = @"\n";
	for(int y = 0; y < maxZ; ++y) {
		for(int x = 0; x < maxX; ++x) {
			NSString* value = [NSString stringWithFormat:@" %2d", [[landscape objectAtIndex:x + y * maxX] height]];
			line = [line stringByAppendingString:value];
		}
		line = [line stringByAppendingString:@"\n"];
	}
	return line;
}

#pragma mark -
#pragma mark Implementation
-(void)generateWithTrees:(BOOL)wantTrees
{
	srandom(level);
	[landscape removeAllObjects];
	
	for(int z = 0; z < maxZ; ++z) {
		for(int x = 0; x < maxX; ++x) {
			[landscape addObject:[[[JWSquare alloc] initWithPositionX:x z:z height:0 number:z * maxX + x] autorelease]];
		}
	}

	int height = max(maxX, maxZ) / 2;
	int maxHeight = 0;
	
	while(maxHeight < height) {
		int length_ = randomInt(maxX) + 1;
		int width_ = randomInt(maxZ) + 1;
		int left = randomInt(maxX + (length_ - 1)) - (length_ - 1);
		int top = randomInt(maxZ + (width_ - 1)) - (width_ - 1);
		
		for(int y = left; y < left + width_; ++y) {
			for(int x = top; x < top + length_; ++x) {
				if(x >= 0 && y >= 0 && x < maxX && y < maxZ) {
					JWSquare* square = [landscape objectAtIndex:x + y * maxX];
					int newHeight = [square incrementHeight];
					if(newHeight > maxHeight) {
						maxHeight = newHeight;
					}
				}
			}
		}
	}
	[self analyzeLandscape];
	if(wantTrees) {
		[self plantTrees];
	}
}

#pragma mark Getters and Setters
-(int)size
{
	return maxX > maxZ ? maxX: maxZ;
}

-(CGSize)dimensions
{
	CGSize size;
	size.width = maxZ;
	size.height = maxX;
	return size;
}

#pragma mark Landscape analysis
-(void)analyzeLandscape
{		
	NSInteger lowestFlat = INT_MAX;
	NSInteger highestFlat = 0;
	
	// Set flatness information.	
	for(int z = 0; z < maxZ; ++z) {
		for(int x = 0; x < maxX; ++x) {
			JWSquare* square = [landscape objectAtIndex:z * maxX+ x];
			int height = [square height];
			BOOL flat = NO;
			if(landscapeMode == JWLandscapeCubic) {
				flat = YES;
			} else {
				if(x == 0 || height == [[landscape objectAtIndex:z * maxX+ x - 1] height]) {
					flat = YES;
				}
				if(landscapeMode == JWLandscapeSmooth && z != maxZ - 1) {
					if(height != [[landscape objectAtIndex:(z + 1) * maxX+ x] height] ||
					   (x != 0 && height != [[landscape objectAtIndex:(z + 1) * maxX+ x - 1] height])) {
						flat = NO;
					}
				}
			}
			[square setFlat:flat];
			
		}
	}
	
	// Store the highest and lowest squares.
	for(int z = 0; z < maxZ; ++z) {
		for(int x = 0; x < maxX; ++x) {
			JWSquare* square = [landscape objectAtIndex:z * maxX+ x];
			int height = square.height;
			
			if(square.flat == YES) {
				if(height > highestFlat) {
					[highSquares removeAllObjects];
					[highSquares addObject:square];
					highestFlat = height;
				} else if(height < lowestFlat && [self hasFlatNeighbour:square]) {
					[lowSquares removeAllObjects];
					[lowSquares addObject:square];
					lowestFlat = height;
				} else if(height == highestFlat) {
					[highSquares addObject:square];
				} else if(height == lowestFlat && [self hasFlatNeighbour:square]) {
					[lowSquares addObject:square];
				}
			}
		}
	}
}

-(BOOL)hasFlatNeighbour:(JWSquare*)square
{
	BOOL flatNeighbour = NO;
	
	JWSquare* neighbour = nil;
	if(square.x > 0) {
		neighbour = [self getSquareAtX:square.x - 1 z:square.z];
		flatNeighbour = neighbour.flat;
	}
	
	if(!flatNeighbour) {
		if(square.x < maxX - 1) {
			neighbour = [self getSquareAtX:square.x + 1 z:square.z];
			flatNeighbour = neighbour.flat;
		}
	}
		
	if(!flatNeighbour) {
		if(square.z > 0) {
			neighbour = [self getSquareAtX:square.x z:square.z - 1];
			flatNeighbour = neighbour.flat;
		}
	}
	
	if(!flatNeighbour) {
		if(square.z < maxZ - 1) {
			neighbour = [self getSquareAtX:square.x z:square.z + 1];
			flatNeighbour = neighbour.flat;
		}
	}
	
	return flatNeighbour;
}

#pragma mark Utility Methods

-(void)calculateSquareVertices
{
	JWSquare* square;
	for(square in landscape) {
		int x = [square x];
		int z = [square z];
	
		GLfloat height = [square height];
		GLfloat previousHeight;
		
		vec3_t a;
		vec3_t b;
		vec3_t c;
		vec3_t d;
			
		if(landscapeMode == JWLandscapeCubic || x == 0) {
			previousHeight = height;
		} else {
			previousHeight = [[landscape objectAtIndex:z * maxX+ x - 1] height];
		}
		
		// If we're in smooth mode, then join adjacent strips without a gap.
		if(landscapeMode == JWLandscapeSmooth && z != maxZ - 1) {
			GLfloat deeperHeight = [[landscape objectAtIndex:(z + 1) * maxX+ x] height];
			GLfloat previousDeeperHeight;
			if(x != 0) {
				previousDeeperHeight = [[landscape objectAtIndex:(z + 1) * maxX+ x - 1] height];
			} else {
				previousDeeperHeight = deeperHeight;
			}
			a[0] = x; a[1] = previousDeeperHeight; a[2] = z + 1.0f;
			b[0] = x + 1.0f; b[1] = deeperHeight; b[2] = z + 1.0f;
		} else {
			a[0] = x; a[1] = previousHeight; a[2] = z + 1.0f;
			b[0] = x + 1.0f; b[1] = height; b[2] = z + 1.0f;
		}
		
		c[0] = x + 1.0f; c[1] = height; c[2] = z;
		d[0] = x; d[1] = previousHeight; d[2] = z;
		
		[square setVerticesA:a B:b C:c D:d];
	}
}

-(void)plantTrees
{
	int treeCount = [[self emptySquares] count] / kothTreeFraction;
	if(treeCount > MAX_TREES) {
		treeCount = MAX_TREES;
	}
	
	for(int i = 0; i < treeCount; ++i) {
		[self plantTree];
	}
}

-(void)plantTree
{
	NSMutableArray* empties = [self emptySquares];
	
	int x = randomInt([empties count]);
	JWSquare* square = [empties objectAtIndex:x];	
	[empties removeObject:square];
	
	JWTree* tree = [[[JWTree alloc] initWithSquare:square] autorelease];
	[[tree manager] addEntity:tree];
	
}

-(NSMutableArray*)emptySquares
{
	NSMutableArray* empties = [[[NSMutableArray alloc] initWithCapacity:[landscape count]] autorelease];
	for(JWSquare* s in landscape) {
		if(s.flat && (!s.tree && !s.robot && !s.player && !s.sentinel && ![s.boxes count])) {
			[empties addObject:s];
		}
	}
	return empties;
}

-(void)populateLandscape:(NSArray*)positions type:(Class)aClass
{
	for(JWLandscapeCoords* position in positions) {
		JWSquare* square = [self getSquareAtX:position.x z:position.z];
		for(int i = 0; i < position.count; ++i) {
			NSObject<JWEntity>* object = [[[aClass alloc] initWithSquare:square] autorelease];
			NSObject<JWManager>* manager = [object manager];
			[manager addEntity:object];
		}
	}
}

#pragma mark Level properties
-(void)setMaxXandMaxZ
{	
	srandom(level);
	if(level < 5) {
		maxX = maxZ = 8;
	} else if(level < 20) {
		maxX = maxZ = 16;
	} else if(level < 50) {
		maxX = maxZ = 32;
		if(random() & 1) {
			maxZ = 16;
		}
	} else if(level < 100) {
		maxX = maxZ = 48;
		if(random() & 1) {
			if(random() & 1) {
				maxZ = 16;
			} else {
				maxZ = 32;
			}
		}
	} else {
		static int MIN_SIZE = 16;
		static int MAX_SIZE = 64;
		
		maxX = maxZ = randomInt(MAX_SIZE - MIN_SIZE) + MIN_SIZE;
		if((random() & 3) == 3) {
			maxZ = randomInt(MAX_SIZE - MIN_SIZE) + MIN_SIZE;
		}
	}
}

-(void)setSentinelCount
{
	srandom(level);
	
	sentinelCount = 1;
	NSUInteger extraSentinels = randomInt(level / 50);
	sentinelCount += extraSentinels;
	
	if(sentinelCount > MAX_SENTINELS) {
		sentinelCount = MAX_SENTINELS;
	}
}

-(void)addExtraSentinels
{
	NSMutableArray* empty = [self emptySquares];
	
	for(NSInteger i = 0; i < sentinelCount - 1; ++i) {

		NSUInteger randomIndex = randomInt([empty count]);
		
		JWSquare* square = [empty objectAtIndex:randomIndex];
		[empty removeObject:square];
		
		JWSentinel* sentinel = [[[JWSentinel alloc] initWithSquare:square] autorelease];
		sentinel.sentinelId = i + 1;
		[[sentinel manager] addEntity:sentinel];
	}
}


#pragma mark Geting squares.
-(JWSquare*)getSquareAtX:(NSUInteger)x z:(NSUInteger)z
{
	JWSquare* square = nil;
	if(x < maxX && z < maxZ) {
		square = [landscape objectAtIndex:z * maxX+ x];
	}
	
	return square;
}

-(NSArray*)getTreeSquaresNearPlayer
{
	NSMutableArray* squares = [NSMutableArray arrayWithCapacity:kothMeanieRange * kothMeanieRange];
	NSInteger playerX = rint(Player.position.origin[0]);
	NSInteger playerZ = rint(-Player.position.origin[1]);
	
	// Ha! Case sensitivity for the win.
	NSInteger minx = playerX - kothMeanieRange;
	NSInteger maxx = playerX + kothMeanieRange;
	NSInteger minz = playerZ - kothMeanieRange;
	NSInteger maxz = playerZ + kothMeanieRange;
	
	for(NSInteger x = minx; x <= maxx; ++x) {
		for(NSInteger z = minz; z <= maxz; ++z) {
			if(x >= 0 && x < maxX && z >= 0 && z < maxZ) {
				JWSquare* square = [self getSquareAtX:x z:z];
				if(square.tree) {
					[squares addObject:square];
				}
			}
		}
	}
	[squares sortUsingSelector:@selector(sortByHeight:)];
	
	return squares;
}

-(NSArray*)getSquaresLowerThanHeight:(NSUInteger)height
{
	NSMutableArray* squares = [NSMutableArray arrayWithCapacity:[landscape count]];
	JWSquare* square;
	
	for(square in landscape) {
		if(square.height < height) {
			if(square.flat && !square.player && !square.sentinel && !square.robot && ![square.boxes count] && !square.tree) {
				[squares addObject:square];
			}
		}
	}
	if([squares count] == 0) {
		squares = (NSMutableArray*)[self getSquaresLowerThanHeight:++height];
	}
	
	return squares;
}


#pragma mark -
#pragma mark Persistence
-(id)initWithCoder:(NSCoder*)decoder
{
	BOOL saveExists = [decoder containsValueForKey:@"level"];
	if(!saveExists) {
		level = 1;
		maxZ = 32;
		maxX= 32;
	} else {
		level = [decoder decodeIntegerForKey:@"level"];
		maxZ = [decoder decodeIntegerForKey:@"maxZ"];
		maxX= [decoder decodeIntegerForKey:@"maxX"];
	}
	
	self = [self initWithLevel:level populate:NO];
	
	NSArray* archivedTrees = [decoder decodeObjectForKey:@"trees"];
	NSArray* archivedBoxes = [decoder decodeObjectForKey:@"boxes"];
	NSArray* archivedRobots = [decoder decodeObjectForKey:@"robots"];
	NSArray* archivedSentinels = [decoder decodeObjectForKey:@"sentinels"];
	[JWSentinel resting:[decoder decodeBoolForKey:@"sentinelState"]];
	 
	[self populateLandscape:archivedTrees type:[JWTree class]];
	[self populateLandscape:archivedBoxes type:[JWBox class]];
	[self populateLandscape:archivedRobots type:[JWRobot class]];
	[self populateLandscape:archivedSentinels type:[JWSentinel class]];
	

	JWLandscapeCoords* high = [decoder decodeObjectForKey:@"high"];
	JWSquare* square = [self getSquareAtX:high.x z:high.z];
	highSquare = square;

	JWLandscapeCoords* selected = [decoder decodeObjectForKey:@"selected"];
	square = [self getSquareAtX:selected.x z:selected.z];
	selectedSquare = square;
	square.selected = YES;
	
	return self;
}

-(void)encodeWithCoder:(NSCoder*)encoder
{
	[encoder encodeInteger:level forKey:@"level"];
	[encoder encodeInteger:maxZ forKey:@"maxZ"];
	[encoder encodeInteger:maxX forKey:@"maxX"];
	
	
	NSMutableArray* archivedTrees = [[[NSMutableArray alloc] initWithCapacity:MAX_TREES] autorelease];
	NSMutableArray* archivedBoxes = [[[NSMutableArray alloc] initWithCapacity:MAX_TREES] autorelease];
	NSMutableArray* archivedRobots = [[[NSMutableArray alloc] initWithCapacity:MAX_TREES] autorelease];
	NSMutableArray* archivedSentinels = [[[NSMutableArray alloc] initWithCapacity:MAX_TREES] autorelease];
	
	for(JWSquare* square in landscape) {
		if(square.tree) [archivedTrees addObject:[[[JWLandscapeCoords alloc] initWithSquare:square] autorelease]];
		if([square.boxes count]) [archivedBoxes addObject:[[[JWLandscapeCoords alloc] initWithSquare:square] autorelease]];
		if(square.robot) [archivedRobots addObject:[[[JWLandscapeCoords alloc] initWithSquare:square] autorelease]];
		if(square.sentinel) [archivedSentinels addObject:[[[JWLandscapeCoords alloc] initWithSquare:square] autorelease]];
	}
	
	[encoder encodeObject:archivedTrees forKey:@"trees"];
	[encoder encodeObject:archivedBoxes forKey:@"boxes"];
	[encoder encodeObject:archivedRobots forKey:@"robots"];
	[encoder encodeObject:archivedSentinels forKey:@"sentinels"];
	[encoder encodeBool:[JWSentinel resting] forKey:@"sentinelState"];
	
	JWLandscapeCoords* selected = [[[JWLandscapeCoords alloc] initWithSquare:selectedSquare] autorelease];
	JWLandscapeCoords* high = [[[JWLandscapeCoords alloc] initWithSquare:highSquare] autorelease];
	[encoder encodeObject:selected forKey:@"selected"];
	[encoder encodeObject:high forKey:@"high"];
}





@end
